﻿/******************************************************************************
* SunnyUI.FrameDecoder 开源TCP、串口数据解码库。
* CopyRight (C) 2012-2023 ShenYongHua(沈永华).
* QQ群：56829229 QQ：17612584 EMail：SunnyUI@qq.com
*
* Blog:   https://www.cnblogs.com/yhuse
* Gitee:  https://gitee.com/yhuse/SunnyUI
* GitHub: https://github.com/yhuse/SunnyUI
*
* SunnyUI.dll can be used for free under the MulanPSL2 license.
* If you use this code, please keep this note.
* 如果您使用此代码，请保留此说明。
******************************************************************************
* 文件名称: UdpSender.cs
* 文件说明: UDP扩展类
* 当前版本: V1.0
* 创建日期: 2022-11-01
*
* 2022-11-01: V1.0.0 增加文件说明
******************************************************************************/

using System;
using System.Net;
using System.Net.Sockets;

namespace Sunny.Udp
{
    /// <summary>
    /// UDP数据发送类
    /// </summary>
    public class UdpClient : IDisposable
    {
        private System.Net.Sockets.UdpClient _client;
        private IPEndPoint _endPoint;

        /// <summary>
        /// 通讯套接字
        /// </summary>
        public Socket Client => _client?.Client;

        /// <summary>
        /// 是否开启广播模式
        /// </summary>
        public bool EnableBroadcast
        {
            get => _client.EnableBroadcast;
            set => _client.EnableBroadcast = value;
        }

        private string name;

        /// <summary>
        /// 名称
        /// </summary>
        public string Name
        {
            get => name ?? this.GetType().Name;
            set => name = value;
        }

        /// <summary>
        /// 发送至此IP
        /// </summary>
        private string IpAddress { get; set; }

        /// <summary>
        /// 发送至此端口
        /// </summary>
        private int Port { get; set; }

        /// <summary>
        /// 发送缓冲大小
        /// </summary>
        public int SendBufferSize
        {
            get => _client.Client.SendBufferSize;
            set => _client.Client.SendBufferSize = value;
        }

        /// <summary>
        /// 标签
        /// </summary>
        public object Tag { get; set; }

        /// <summary>
        /// 错误回调事件
        /// </summary>
        public event OnUdpSocketError OnError;

        /// <summary>
        /// 错误回调
        /// </summary>
        /// <param name="e"></param>
        protected virtual void DoError(Exception e)
        {
            Console.WriteLine(Name + " Error: " + e.Message);
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        public UdpClient()
        {
            _client = new System.Net.Sockets.UdpClient();
            SendBufferSize = 64 * 1024;
        }

        /// <summary>
        /// 绑定本地端口
        /// </summary>
        /// <param name="localPort">绑定本地端口</param>
        public void Bind(int localPort)
        {
            _client?.Close();
            _client = null;

            try
            {
                _client = new System.Net.Sockets.UdpClient(new IPEndPoint(IPAddress.Any, localPort));
            }
            catch (Exception e)
            {
                Console.WriteLine($"Bind Error: {e.Message}");
                _client = new System.Net.Sockets.UdpClient();
            }
        }

        /// <summary>
        /// 绑定本地端口
        /// </summary>
        /// <param name="localIP">绑定本地IP地址</param>
        /// <param name="localPort">绑定本地端口</param>
        public void Bind(string localIP, int localPort)
        {
            _client?.Close();
            _client = null;

            try
            {
                _client = new System.Net.Sockets.UdpClient(new IPEndPoint(IPAddress.Parse(localIP), localPort));
            }
            catch (Exception e)
            {
                Console.WriteLine($"Bind Error: {e.Message}");
                _client = new System.Net.Sockets.UdpClient();
            }
        }

        /// <summary>
        /// 析构函数
        /// </summary>
        public virtual void Dispose()
        {
            _client?.Close();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dgram"></param>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        public void Send(ReadOnlySpan<byte> dgram, string ip, int port)
        {
            Send(dgram, 0, dgram.Length, ip, port);
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="dgram">数据</param>
        /// <param name="offset">起始位置</param>
        /// <param name="bytes">长度</param>
        /// <param name="ip">发送到此IP地址</param>
        /// <param name="port">发送到此端口</param>
        public void Send(ReadOnlySpan<byte> dgram, int offset, int bytes, string ip, int port)
        {
            SetRemoteIPEndPort(ip, port);

            if (dgram == null || dgram.Length == 0)
            {
                throw new ArgumentNullException("data");
            }

            if (_endPoint == null)
            {
                throw new ArgumentNullException("endPoint is null.");
            }

            if (offset < 0)
            {
                throw new ArgumentOutOfRangeException("offset must be greater than or equal to zero.");
            }

            if (offset + bytes > dgram.Length)
            {
                throw new ArgumentOutOfRangeException("bytes must be less than or equal data length.");
            }

            Send(dgram.Slice(offset, bytes).ToArray(), ip, port);
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="dgram">数据</param>
        /// <param name="ip">发送到此IP地址</param>
        /// <param name="port">发送到此端口</param>
        public void Send(byte[] dgram, string ip, int port)
        {
            Send(dgram, 0, dgram.Length, ip, port);
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="dgram">数据</param>
        /// <param name="offset">起始位置</param>
        /// <param name="bytes">长度</param>
        /// <param name="ip">发送到此IP地址</param>
        /// <param name="port">发送到此端口</param>
        public void Send(byte[] dgram, int offset, int bytes, string ip, int port)
        {
            SetRemoteIPEndPort(ip, port);

            if (dgram == null || dgram.Length == 0)
            {
                throw new ArgumentNullException("data");
            }

            if (_endPoint == null)
            {
                throw new ArgumentNullException("endPoint is null.");
            }

            if (offset < 0)
            {
                throw new ArgumentOutOfRangeException("offset must be greater than or equal to zero.");
            }

            if (offset + bytes > dgram.Length)
            {
                throw new ArgumentOutOfRangeException("bytes must be less than or equal data length.");
            }

            try
            {
                if (offset == 0 && bytes <= dgram.Length)
                    _client.Send(dgram, bytes, _endPoint);
                else
                    _client.Send(dgram.AsSpan().Slice(offset, bytes).ToArray(), bytes, _endPoint);
            }
            catch (Exception ex)
            {
                DoError(ex);
                OnError?.Invoke(this, ex);
            }
        }

        /// <summary>
        /// 设置接收方IP地址和端口
        /// </summary>
        /// <param name="ip">接收方IP地址</param>
        /// <param name="port">接收方端口</param>
        private void SetRemoteIPEndPort(string ip, int port)
        {
            if (string.IsNullOrEmpty(ip)) return;
            if (!IsIP4(ip)) return;
            if (ip.Equals("0.0.0.0")) return;
            if (port <= 0 || port >= 65535) return;
            if (ip.Contains("255")) EnableBroadcast = true;

            if (IpAddress != ip || Port != port)
            {
                IpAddress = ip;
                Port = port;

                try
                {
                    _endPoint = new IPEndPoint(IPAddress.Parse(IpAddress), Port);
                }
                catch (Exception ex)
                {
                    _endPoint = null;
                    DoError(ex);
                    OnError?.Invoke(this, ex);
                }
            }
        }

        /// <summary>
        /// 字符串是否为IP地址
        /// </summary>
        /// <param name="s">字符串</param>
        /// <returns>结果</returns>
        private bool IsIP4(string s)
        {
            string[] strs = s.Split('.');
            if (strs.Length != 4)
            {
                return false;
            }

            foreach (string str in strs)
            {
                if (int.TryParse(str, out int value))
                {
                    if (value < 0) return false;
                    if (value > 255) return false;
                }
            }

            return true;
        }
    }
}
